//
//  DYLaunchEventManager.m
//  DYZB
//
//  Created by 吴新庭 on 2018/7/20.
//  Copyright © 2018年 mydouyu. All rights reserved.
//

#import "DYLaunchEventManager.h"
#import <objc/runtime.h>

@interface DYLaunchAction : NSObject

@property (nonatomic, assign) DYLaunchEvent event;
@property (nonatomic, copy) DYLaunchActionBlock actionBlock;

@end

@implementation DYLaunchAction
@end

@interface DYLaunchEventManager ()

@property (nonatomic, assign) CFRunLoopObserverRef observer;

@property (nonatomic, strong) NSMutableArray<DYLaunchAction *> *actions;

@end

@implementation DYLaunchEventManager

+ (DYLaunchEventManager *)sharedInstance {
    static DYLaunchEventManager *inst = nil;
    static dispatch_once_t token;
    dispatch_once(&token, ^{
        inst = [[DYLaunchEventManager alloc] init];
        [inst listenRunloopIdle];
    });
    return inst;
}

- (void)act:(void (^)(void))action beforeEvent:(DYLaunchEvent)event {
    if (event <= self.event) {
        // 该事件已经到达，立即执行
        action();
    } else {
        DYLaunchAction *launchAction = [[DYLaunchAction alloc] init];
        launchAction.event = event;
        launchAction.actionBlock = action;
        
        // 系统的排序算法非稳定排序
        NSInteger idx = [self.actions indexOfObjectPassingTest:^BOOL(DYLaunchAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if (launchAction.event < obj.event) {
                *stop = YES;
                return YES;
            }
            return NO;
        }];
        if (idx == NSNotFound) {
            [self.actions addObject:launchAction];
        } else {
            [self.actions insertObject:launchAction atIndex:idx];
        }
    }
}

#pragma mark - Set
- (void)setEvent:(DYLaunchEvent)event {
    if (_event == event) {
        return;
    }
    
    _event = event;
    [self schedule];
}

#pragma mark - Private
- (void)listenRunloopIdle {
    __weak typeof(self) wSelf = self;
    self.observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        if (activity & kCFRunLoopBeforeWaiting) {
            [wSelf scheduleIdle];
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.observer, kCFRunLoopDefaultMode);
}

- (void)schedule {
    NSInteger i = 0;
    for (; i < self.actions.count; i++) {
        DYLaunchAction *action = self.actions[i];
        if (action.event > self.event) {
            break;
        }
        action.actionBlock();
    }
    [self.actions removeObjectsInRange:NSMakeRange(0, i)];
}

/**
 如果是空闲，不需要关注时间是否到达，可以提前完成提交的任务
 */
- (void)scheduleIdle {
    
    DYLaunchAction *action = self.actions.firstObject;
    if (action) {
        if (action.event < DYLaunchEventFuture) {
            // event非Future的action，只关注runloop是否空闲，不关注当前event
            action.actionBlock();
            [self.actions removeObject:action];
#ifdef DEBUG
            NSLog(@"***** scheduleIdle : action registered before future *****");
#endif
        }
        else {
            // event为Future的action，在当前event触达HomeDisplayed后的runloop空闲时执行
            if (self.event < DYLaunchEventHomeDisplayed) {
                return;
            }
            action.actionBlock();
            [self.actions removeObject:action];
#ifdef DEBUG
            NSLog(@"***** scheduleIdle : action registered future *****");
#endif
        }
    }
}

#pragma mark - Get
- (NSMutableArray<DYLaunchAction *> *)actions {
    if (!_actions) {
        _actions = [NSMutableArray array];
    }
    return _actions;
}

@end

#pragma mark - NSObject Launch Event
static NSString *const kObjectEventKey = @"kObjectEventKey";
@interface NSObject ()

@property (nonatomic, strong) NSMutableArray *mArr;

@end

@implementation NSObject (LaunchEvent)

- (void)pushEvent:(NSObject *)event {
    [self.mArr addObject:event];
}

- (NSObject *)popEvent {
    NSObject *obj = self.mArr.lastObject;
    [self.mArr removeLastObject];
    return obj;
}

- (NSMutableArray *)mArr {
    NSMutableArray *arr = objc_getAssociatedObject(self, &kObjectEventKey);
    if (!arr) {
        arr = [NSMutableArray array];
        objc_setAssociatedObject(self, &kObjectEventKey, arr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return arr;
}

@end
